package comm

import (
	"crypto/md5"
	"fmt"
	"log"
	"lottery/conf"
	"lottery/models"
	"net"
	"net/http"
	"net/url"
	"strconv"
)

// 获取IP的方法
func ClientIp(r *http.Request) string {
	host, _, _ := net.SplitHostPort(r.RemoteAddr) // 从用户的ip端口中获取 IP地址
	return host
}

// 跳转方法 ：gin 中有封装好的方法
//func Redirect(writer http.ResponseWriter, url string) {
//	writer.Header().Add("Location", url)
//	writer.WriteHeader(http.StatusFound)
//}

// 验证身份
// 对于cookie 的使用， 框架 context 对象是封装了方法的， 但是这里采用的是较底层原生的方法，
// gin抛开源码看， 底层操作也和这里类似，使用的都是 go语言的原生库
func GetLoginUser(r *http.Request) *models.ObjLoginuser {
	// 从cookie中获取登录用户的 cookie 字段
	c, err := r.Cookie("lottery_loginuser")
	if err != nil {
		return nil
	}
	// 解析 Cookie对象的 Valuse
	params, err := url.ParseQuery(c.Value) // net 包下的 url包解析URL并实现了查询的逸码
	// 拿到数据, 并转换为 int 类型
	uid, err := strconv.Atoi(params.Get("uid"))
	if err != nil {
		return nil
	}
	now, err := strconv.Atoi(params.Get("now"))
	// 判断是否过期
	if err != nil || NowUnix()-now > 86400 { // 判断是否过期： 默认的登录时间是 1秒
		return nil
	}

	// 上述都没问题，那么我们将构建一个 loginuser 对象了
	loginUser := &models.ObjLoginuser{}
	loginUser.Uid = uid
	loginUser.Username = params.Get("username")
	loginUser.Now = now
	loginUser.Ip = ClientIp(r)
	loginUser.Sign = params.Get("sign") // 签名信息
	// 创建签名，使用下面定义的方法
	sign := createLoginUserSign(loginUser)
	// 验证 cookie 中获得的签名sign ，是否和生成的是一样的
	if sign != loginUser.Sign {
		// 打印日志
		log.Println("func_web.go GetLoginUser createLoginUserSign not signed",
			sign, loginUser.Sign)
		return nil
	}

	return loginUser
}

// 生成加密的 sign 签名
func createLoginUserSign(loginuser *models.ObjLoginuser) string {
	str := fmt.Sprintf("uid=%d&username=%s&secret=%s&now=%d",
		loginuser.Uid, loginuser.Username, conf.CookieSecret, loginuser.Now)
	sign := fmt.Sprintf("%x", md5.Sum([]byte(str))) // 格式化成字符串

	return sign
}

// 设置cookie， 删除 与 生成 user认证的 cookie
func SetLoginuser(writer http.ResponseWriter, loginUser *models.ObjLoginuser) {
	// 验证
	if loginUser == nil || loginUser.Uid < 1 { // 如果对象为 空，或者 uid < 1 删除cookie
		c := &http.Cookie{
			Name:   "lottery_loginuser",
			Value:  "",
			Path:   "/",
			MaxAge: -1, //  MaxAge<0表示立刻删除该cookie，等价于"Max-Age: 0"
		}
		http.SetCookie(writer, c)
		return
	}
	if loginUser.Sign == "" {
		loginUser.Sign = createLoginUserSign(loginUser)
	}

	// cookie对象的 Value 是这个类型的。先创建这个对象，构造这个字段
	// type Values map[string][]string
	params := url.Values{}
	params.Add("uid", strconv.Itoa(loginUser.Uid)) // Add 方法，实际上源码就是一个 append() 操作
	params.Add("now", strconv.Itoa(loginUser.Now))
	params.Add("username", loginUser.Username)
	params.Add("ip", loginUser.Ip)
	params.Add("sign", loginUser.Sign)
	// 创建cookie对象
	c := &http.Cookie{
		Name:  "lottery_loginuser",
		Value: params.Encode(), // 需要user 类型的字符串。
		Path:  "/",
	}
	// 写cookie
	http.SetCookie(writer, c)
}
